package com.hznc.wcs.service.bpm.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.vo.Pagination;
import com.diboot.iam.entity.BaseLoginUser;
import com.diboot.iam.entity.IamUser;
import com.diboot.iam.service.IamUserService;
import com.diboot.iam.util.IamSecurityUtils;

import com.hznc.wcs.constans.BpmConstants;
import com.hznc.wcs.entity.bpm.BpmCategory;
import com.hznc.wcs.entity.bpm.BpmProcessDefinitionInfo;
import com.hznc.wcs.enums.BpmDeleteReasonEnum;
import com.hznc.wcs.enums.BpmProcessInstanceStatusEnum;
import com.hznc.wcs.exception.BusinessException;
import com.hznc.wcs.flowable.event.BpmProcessInstanceEventPublisher;
import com.hznc.wcs.flowable.event.BpmProcessInstanceStatusEvent;
import com.hznc.wcs.service.bpm.BpmCategoryService;
import com.hznc.wcs.service.bpm.BpmProcessDefinitionService;
import com.hznc.wcs.service.bpm.BpmProcessInstanceService;
import com.hznc.wcs.util.BpmnModelUtils;
import com.hznc.wcs.util.DateUtils;
import com.hznc.wcs.util.FlowableUtils;
import com.hznc.wcs.vo.bpm.BpmProcessDefinitionRespVO;
import com.hznc.wcs.vo.bpm.BpmProcessInstanceCreateReqVO;
import com.hznc.wcs.vo.bpm.BpmProcessInstanceReqVO;
import com.hznc.wcs.vo.bpm.BpmProcessInstanceRespVO;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description : 流程实例业务实现
 * @Date : 2024/6/20
 * @Author : ChenJin
 * @Version : v1.0.0
 **/
@Service
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
    
    @Resource
    private RepositoryService repositoryService;

    @Resource
    private RuntimeService runtimeService;
    
    @Resource
    private HistoryService historyService;
    
    @Resource
    private TaskService taskService;
    
    @Resource
    private BpmCategoryService bpmCategoryService;
    
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    
    @Resource
    private IamUserService iamUserService;

    @Resource
    private BpmProcessInstanceEventPublisher processInstanceEventPublisher;

    @Override
    public List<BpmProcessInstanceRespVO> getBpmProcessInstanceList(BpmProcessInstanceReqVO reqVO, Pagination pagination) {
        BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser();
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceTenantId(currentUser.getTenantId())
                .orderByProcessInstanceStartTime().desc();
        if (StringUtils.isNotEmpty(reqVO.getStartUserId())){
            query.startedBy(reqVO.getStartUserId());
        }
        if (StringUtils.isNotEmpty(reqVO.getName())){
            query.processInstanceNameLike("%"+reqVO.getName()+"%");
        }
        if (StringUtils.isNotEmpty(reqVO.getProcessDefinitionId())){
            query.processDefinitionId(reqVO.getProcessDefinitionId());
        }
        if (StringUtils.isNotEmpty(reqVO.getCategory())){
            query.processDefinitionCategory(reqVO.getCategory());
        }
        if (ObjectUtils.isNotEmpty(reqVO.getStatus())){
            query.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,reqVO.getStatus());
        }
        if (ObjectUtils.isNotEmpty(reqVO.getCreateTimeStart())&&ObjectUtils.isNotEmpty(reqVO.getCreateTimeEnd())){
            query.startedAfter(DateUtils.of(reqVO.getCreateTimeStart()));
            query.startedBefore(DateUtils.of(reqVO.getCreateTimeEnd()));
        }
        long count = query.count();
        pagination.setTotalCount(count);
        if (count<=0){
            return new ArrayList<>();
        }
        List<HistoricProcessInstance> historicProcessInstances = query.listPage(pagination.getPageIndex() - 1, pagination.getPageSize());
        //查询流程实例关联任务实例
        //封装以processInstanceId为key,Task为value的Map
        List<String> instanceIds = historicProcessInstances.stream().map(HistoricProcessInstance::getId).collect(Collectors.toList());
        List<Task> taskList = taskService.createTaskQuery().processInstanceIdIn(instanceIds).list();
        Map<String, List<Task>> instanceIdToTraskMap = taskList.stream().collect(Collectors.groupingBy(Task::getProcessInstanceId, Collectors.mapping(task -> task,Collectors.toList())));
        //查询流程实例所属流程定义
        //封装以processDefinitionId为key,ProcessDefinition为value的Map
        Set<String> processDefinitionIds = historicProcessInstances.stream().map(HistoricProcessInstance::getProcessDefinitionId).collect(Collectors.toSet());
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery().processDefinitionIds(processDefinitionIds).list();
        Map<String, ProcessDefinition> definitionMap = processDefinitionList.stream().collect(Collectors.toMap(ProcessDefinition::getId, definition -> definition));
        //查询流程分类
        //封装以categoryId为key,BpmCategory为value的Map
        List<String> categoryIds = definitionMap.values().stream().filter(item->StringUtils.isNotEmpty(item.getCategory())).map(ProcessDefinition::getCategory).collect(Collectors.toList());
        List<BpmCategory> bpmCategoryList = bpmCategoryService.getEntityListByIds(categoryIds);
        Map<String, BpmCategory> categoryMap = bpmCategoryList.stream().collect(Collectors.toMap(BpmCategory::getId, bpmCategory -> bpmCategory));
        //查询发起人信息
        //封装以startUserId为key,IamUser为value的Map
        List<String> startUserIds = historicProcessInstances.stream().map(HistoricProcessInstance::getStartUserId).distinct().collect(Collectors.toList());
        List<IamUser> startUserList = iamUserService.getEntityListByIds(startUserIds);
        Map<String, IamUser> startUserMap = startUserList.stream().collect(Collectors.toMap(IamUser::getId, iamUser -> iamUser));
        return historicProcessInstances.stream().map(instance->{
            BpmProcessInstanceRespVO bpmProcessInstanceRespVO = BeanUtils.convert(instance, BpmProcessInstanceRespVO.class);
            bpmProcessInstanceRespVO.setStatus(FlowableUtils.getProcessInstanceStatus(instance));
            bpmProcessInstanceRespVO.setStatusLabel(BpmProcessInstanceStatusEnum.descOfStatus(bpmProcessInstanceRespVO.getStatus()));
            bpmProcessInstanceRespVO.setModelKey(instance.getProcessDefinitionKey());
            if (StringUtils.isNotEmpty(definitionMap.get(instance.getProcessDefinitionId()).getCategory())){
                bpmProcessInstanceRespVO.setCategory(definitionMap.get(instance.getProcessDefinitionId()).getCategory());
                bpmProcessInstanceRespVO.setCategoryName(categoryMap.get(definitionMap.get(instance.getProcessDefinitionId()).getCategory()).getName());
            }
            bpmProcessInstanceRespVO.setTasks(BeanUtils.convertList(instanceIdToTraskMap.get(instance.getId()), BpmProcessInstanceRespVO.Task.class));
            bpmProcessInstanceRespVO.setStartUser(BeanUtils.convert(startUserMap.get(instance.getStartUserId()),BpmProcessInstanceRespVO.User.class));
            return bpmProcessInstanceRespVO;
        }).collect(Collectors.toList());
    }

    @Override
    public BpmProcessInstanceRespVO getBpmProcessInstance(String instanceId) {
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).includeProcessVariables().singleResult();
        if (processInstance == null) {
            return null;
        }
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
        String bpmnXml = BpmnModelUtils.getBpmnXml(repositoryService.getBpmnModel(processDefinition.getId()));
        LambdaQueryWrapper<BpmProcessDefinitionInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BpmProcessDefinitionInfo::getProcessDefinitionId,processDefinition.getId()).eq(BpmProcessDefinitionInfo::isDeleted,0);
        BpmProcessDefinitionInfo bpmProcessDefinitionInfo = bpmProcessDefinitionService.getSingleEntity(queryWrapper);
        IamUser startUser = iamUserService.getEntity(processInstance.getStartUserId());
        BpmProcessInstanceRespVO bpmProcessInstanceRespVO = BeanUtils.convert(processInstance,BpmProcessInstanceRespVO.class);
        bpmProcessInstanceRespVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
        bpmProcessInstanceRespVO.setStatusLabel(BpmProcessInstanceStatusEnum.descOfStatus(bpmProcessInstanceRespVO.getStatus()));
        bpmProcessInstanceRespVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
        BpmProcessDefinitionRespVO bpmProcessDefinitionRespVO = BeanUtils.convert(processDefinition, BpmProcessDefinitionRespVO.class);
        BeanUtils.copyProperties(bpmProcessDefinitionInfo,bpmProcessDefinitionRespVO);
        bpmProcessDefinitionRespVO.setBpmnXml(bpmnXml);
        bpmProcessInstanceRespVO.setProcessDefinition(bpmProcessDefinitionRespVO);
        bpmProcessInstanceRespVO.setStartUser(BeanUtils.convert(startUser, BpmProcessInstanceRespVO.User.class));
        List<Task> taskList = taskService.createTaskQuery().processInstanceId(instanceId).list();
        bpmProcessInstanceRespVO.setTasks(BeanUtils.convertList(taskList,BpmProcessInstanceRespVO.Task.class));
        return bpmProcessInstanceRespVO;
    }

    @Override
    @Transactional
    public String createProcessInstance(BpmProcessInstanceCreateReqVO createReqVO) {
        BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser();
        if(ObjectUtils.isEmpty(createReqVO)){
            throw new BusinessException("请求参数有误");
        }
        //获取流程定义
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(createReqVO.getProcessDefinitionId());
        if(ObjectUtils.isEmpty(processDefinition)){
            throw new BusinessException("流程定义不存在");
        }
        if (processDefinition.isSuspended()){
            throw new BusinessException("流程已挂起");
        }
        //创建流程实例
        Map<String,Object> variables = createReqVO.getVariables();
        if (ObjectUtils.isEmpty(variables)){
            variables = new HashMap<>();
        }
        FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下，避免 ProcessInstance 系统级的变量被占用
        variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态：审批中
                BpmProcessInstanceStatusEnum.RUNNING.getStatus());
        Authentication.setAuthenticatedUserId(currentUser.getId());
        ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
                .processDefinitionId(processDefinition.getId())
                .businessKey(createReqVO.getBusinessKey())
                .name(processDefinition.getName().trim())
                .variables(variables)
                .start();
        Authentication.setAuthenticatedUserId(null);
        return instance.getId();
    }

    @Override
    @Transactional
    public void updateProcessInstanceWhenReject(String id, String reason) {
        //更新流程实例 status
        runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());
        //删除流程实例，以实现驳回任务时，取消整个审批流程
        runtimeService.deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();
        //发送流程实例的状态事件,主要用来流程结束后,处理相关业务逻辑
        BpmProcessInstanceStatusEvent bpmProcessInstanceStatusEvent = new BpmProcessInstanceStatusEvent(this)
                .setId(processInstance.getId())
                .setStatus(BpmProcessInstanceStatusEnum.REJECT.getStatus())
                .setProcessDefinitionKey(processInstance.getProcessDefinitionKey())
                .setBusinessKey(processInstance.getBusinessKey());
        processInstanceEventPublisher.sendProcessInstanceResultEvent(bpmProcessInstanceStatusEvent);
    }

    @Override
    public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) {
        // 1. 判断是否为 Reject 不通过。如果是，则不进行更新.
        // 因为，updateProcessInstanceReject 方法（审批不通过），已经进行更新了
        if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) {
            return;
        }
        // 2. 更新流程实例 status
        runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                BpmProcessInstanceStatusEnum.CANCEL.getStatus());
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(event.getProcessInstanceId()).includeProcessVariables().singleResult();
        // 发送流程实例的状态事件
        BpmProcessInstanceStatusEvent bpmProcessInstanceStatusEvent = new BpmProcessInstanceStatusEvent(this)
                .setId(processInstance.getId())
                .setStatus(BpmProcessInstanceStatusEnum.CANCEL.getStatus())
                .setProcessDefinitionKey(processInstance.getProcessDefinitionKey())
                .setBusinessKey(processInstance.getBusinessKey());
        processInstanceEventPublisher.sendProcessInstanceResultEvent(bpmProcessInstanceStatusEvent);
    }

    @Override
    public void updateProcessInstanceWhenApprove(ProcessInstance instance) {
        // 1. 更新流程实例 status
        runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                BpmProcessInstanceStatusEnum.APPROVE.getStatus());
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instance.getProcessInstanceId()).includeProcessVariables().singleResult();
        //发送流程实例的状态事件,主要用来流程结束后,处理相关业务逻辑
        BpmProcessInstanceStatusEvent bpmProcessInstanceStatusEvent = new BpmProcessInstanceStatusEvent(this)
                .setId(historicProcessInstance.getId())
                .setStatus(BpmProcessInstanceStatusEnum.APPROVE.getStatus())
                .setProcessDefinitionKey(historicProcessInstance.getProcessDefinitionKey())
                .setBusinessKey(historicProcessInstance.getBusinessKey());
        processInstanceEventPublisher.sendProcessInstanceResultEvent(bpmProcessInstanceStatusEvent);
    }

    @Override
    public ProcessInstance getProcessInstance(String id) {
        return runtimeService.createProcessInstanceQuery()
                .includeProcessVariables() 
                .processInstanceId(id)
                .singleResult();
    }

    @Override
    public void cancelProcessInstance(String id,String cancelReason) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
        if (ObjectUtils.isNotEmpty(processInstance)){
            runtimeService.deleteProcessInstance(id,cancelReason);
        }
    }

}
